Skip to content

Production-readiness audit remediation#21

Merged
harrisonk0 merged 8 commits intomainfrom
fix/production-readiness-audit
Mar 22, 2026
Merged

Production-readiness audit remediation#21
harrisonk0 merged 8 commits intomainfrom
fix/production-readiness-audit

Conversation

@harrisonk0
Copy link
Copy Markdown
Owner

@harrisonk0 harrisonk0 commented Mar 22, 2026

This PR replaces #20 and contains the full set of fixes from the production-readiness audit.

Scope:

  • lock core Supabase access back to app-role-aware policies and seed required settings rows
  • replace fragile mark writes with transactional RPC-backed save paths
  • prevent weekly date changes from silently discarding dirty edits
  • remove duplicate auth ownership and dead client-only ceremony in the SPA
  • tighten docs, CI contract checks, and browser smoke coverage around the live Supabase client contract
  • fix the live save_weekly_marks_snapshot function and align the repo docs/tests with that repair

Validation already run:

  • npm run test:run
  • npm run typecheck
  • npm run build
  • npm run check:db-contract
  • npm run test:e2e

Notes:

  • CI and smoke tests validate the client-visible contract against live Supabase data. They do not prove the full live RLS policy graph without privileged inspection.
  • PR Tighten database contract smoke coverage #20 was closed and superseded by this PR so the review surface matches the full audit work.

Summary by CodeRabbit

  • New Features

    • Section settings save/restore and a DB-contract smoke check (new CLI + npm script); weekly marks use snapshot-based saves with save gating and date-change confirmation.
  • Bug Fixes

    • Improved marks persistence, duplicate-date detection, and avoidance of no-op saves.
  • Tests

    • New unit tests for marks helpers and date-change logic; expanded E2E smoke tests and a settings runbook.
  • Documentation

    • Updated pre‑ship/deployment/testing checklists and clarified seeded settings requirements.
  • Chores

    • CI step names clarified and now runs the DB contract check.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bb-manager Ready Ready Preview, Comment Mar 22, 2026 8:26pm

@harrisonk0
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2db80f218

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +265 to +267
if (snapshot.length === 0) {
showToast('No changes to save.', 'info');
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear dirty state when weekly snapshot is empty

When a user makes edits and then restores every value back to its original state, isDirty can still be true (it is set on input events, not recomputed from data). In that case buildWeeklyMarksSnapshot returns an empty list, but this branch only shows a toast and returns, so the page remains stuck in an unsaved state (Save Marks button and leave-confirm prompts persist despite no actual changes). Before this commit, the no-op save path still cleared dirty state via the shared success path.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6d7a9c7. The no-op save path now clears isDirty before returning, so reverting edits back to the original state no longer leaves the page stuck in an unsaved state.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (11)
components/DatePicker.tsx (1)

20-26: Consider removing commented-out code.

The commented code blocks explaining the removed showPicker() functionality could be replaced with a brief inline comment. The git history preserves the original implementation if needed later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/DatePicker.tsx` around lines 20 - 26, Remove the stale
commented-out code related to the removed showPicker() API: delete the commented
declarations and function for inputRef, handleIconClick and the showPicker()
call in DatePicker.tsx, and replace them with a single concise inline comment
noting that programmatic opening via showPicker was removed; reference the
identifiers inputRef, handleIconClick, and showPicker in the comment so future
readers know what was intentionally removed.
scripts/check-db-contract.mjs (1)

81-83: Consider adding error handling for signOut failures.

If signOut() fails (e.g., network issue), any error thrown would mask the original test failure. Consider wrapping in a try-catch to log but not re-throw signOut errors.

🛡️ Proposed defensive signOut
 } finally {
-  await supabase.auth.signOut();
+  try {
+    await supabase.auth.signOut();
+  } catch (signOutError) {
+    console.warn('Failed to sign out during cleanup:', signOutError.message);
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/check-db-contract.mjs` around lines 81 - 83, The finally block
currently awaits supabase.auth.signOut() without guarding against failures,
which could mask an earlier test error; wrap the await supabase.auth.signOut()
call in a try-catch inside the existing finally, await it inside the try, and in
the catch log the sign-out error (e.g., console.error or existing logger) but do
not re-throw so the original test error is preserved; specifically modify the
finally that calls supabase.auth.signOut() to a try { await
supabase.auth.signOut(); } catch (err) { /* log sign-out failure with context */
}.
tests/e2e/02-section-settings-workflow.md (1)

17-23: Add an explicit post-cleanup persistence check.

After restoring the original value, include a save + reload confirmation step so cleanup guarantees are verifiable, not implied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/02-section-settings-workflow.md` around lines 17 - 23, Update the
"Restore the original meeting day value before leaving the page." step in
tests/e2e/02-section-settings-workflow.md to explicitly persist and verify the
cleanup: after resetting the meeting day back to the original value perform the
save action (e.g., trigger the test's save/submit flow and wait for any success
indicator), then reload the page (or navigate away and back) and assert the
meeting day equals the original value; ensure the new assertions replace the
current implied cleanup so the test fails if the restore does not actually
persist.
docs/04-deployment.md (1)

34-37: Optional docs polish: reduce repeated “Verify” openings in checklist items.

This is readable as-is, but varying the leading verb in adjacent steps would make the checklist flow smoother.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/04-deployment.md` around lines 34 - 37, The checklist repeats the verb
"Verify" for consecutive items (the lines starting "Verify sign-in works for
manually provisioned users.", "Verify the production users have the expected
roles in `profiles`.", "Verify `settings` has seeded rows for both `company` and
`junior`.", and "Smoke-test auth, section settings persistence, member CRUD,
marks entry, and dashboard behavior against the live backend."); update these
items to use varied leading verbs (e.g., "Confirm sign-in works...", "Ensure
production users have the expected roles in `profiles`.", "Check `settings`
contains seeded rows for both `company` and `junior`.", and keep or slightly
reword the final "Smoke-test..." line) so the checklist reads more smoothly
while preserving the same checks and scope.
.github/workflows/ci-infrastructure.yml (1)

33-60: Consider gating live-Supabase smoke steps so core CI still runs without secrets.

Right now, missing live secrets fail the job before typecheck/unit/build. If fork PRs or secretless validation are expected, conditionally run the live contract/e2e steps and keep static/unit checks unconditional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci-infrastructure.yml around lines 33 - 60, Make the
live-Supabase steps conditional so forks and secretless CI still run static
checks: add an if expression that requires all four secrets to be present to
each of the live steps ("Check required Supabase client and smoke-test env
vars", "Smoke-test Supabase client startup and runtime config", "Run
client-visible database contract smoke check", "Install Playwright browser
dependencies", and "Run browser smoke tests against live Supabase data"); e.g.
use if: ${{ secrets.VITE_SUPABASE_URL != '' && secrets.VITE_SUPABASE_ANON_KEY !=
'' && secrets.E2E_TEST_EMAIL != '' && secrets.E2E_TEST_PASSWORD != '' }} so the
remaining steps (typecheck, test:run, build) run unconditionally.
services/db.test.ts (1)

209-255: Tighten the members write-path assertions in these tests.

These cases currently prove routing (members table, no RPC) but not the actual write parameters. They would still pass if updateBoy() skipped update(...), or if either method used the wrong eq(...) filters.

🔍 Tighten the assertion surface
   return {
     from,
     rpc,
+    memberUpdate,
+    memberUpdateEqId,
+    memberUpdateEqSection,
+    memberDeleteEqId,
+    memberDeleteEqSection,
     get rpcResponse() {
     expect(supabaseMock.from).toHaveBeenCalledWith('members');
+    expect(supabaseMock.memberUpdate).toHaveBeenCalledWith({
+      name: 'Alex Updated',
+      school_year: '11',
+      section: 'company',
+      squad: 3,
+      is_squad_leader: true,
+    });
+    expect(supabaseMock.memberUpdateEqId).toHaveBeenCalledWith('id', 'member-1');
+    expect(supabaseMock.memberUpdateEqSection).toHaveBeenCalledWith('section', 'company');
     expect(supabaseMock.rpc).not.toHaveBeenCalled();
     expect(supabaseMock.from.mock.calls.filter(([table]) => table === 'marks')).toHaveLength(1);
     expect(supabaseMock.from).toHaveBeenCalledTimes(1);
     expect(supabaseMock.from).toHaveBeenCalledWith('members');
+    expect(supabaseMock.memberDeleteEqId).toHaveBeenCalledWith('id', 'member-1');
+    expect(supabaseMock.memberDeleteEqSection).toHaveBeenCalledWith('section', 'junior');
     expect(supabaseMock.rpc).not.toHaveBeenCalled();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/db.test.ts` around lines 209 - 255, The tests currently only assert
routing to 'members' and absence of RPC but not the exact write arguments;
update the two tests to assert the concrete write parameters: for the updateBoy
test, verify that supabaseMock.from('members').update was called once with the
expected partial payload (only allowed member fields — e.g.,
name/squad/year/is_squad_leader mapping from the input) and that the returned
query used eq('id', 'member-1') and eq('section', 'company'); for the
deleteBoyById test, verify that supabaseMock.from('members').delete was called
with no payload (or called once) and that its query used eq('id', 'member-1')
and eq('section', 'junior'); keep the existing checks that rpc was not called.
Ensure you reference the chain starting from supabaseMock.from so you assert
.update/.delete call arguments and subsequent .eq filters used by updateBoy and
deleteBoyById.
components/weeklyMarksSavePlan.ts (1)

67-67: Type assertion could be made safer.

The cast (markState as JuniorMarkState | undefined) assumes the caller provides the correct type for junior section. If a company-style number is accidentally passed, accessing .uniform and .behaviour will return undefined, which is handled by the fallback, but consider adding a runtime type guard for defense-in-depth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/weeklyMarksSavePlan.ts` at line 67, The current cast of markState
to JuniorMarkState when creating juniorState is unsafe; add a runtime type guard
(e.g., isJuniorMarkState) that checks markState is an object and has string
properties "uniform" and "behaviour", then use that guard instead of a blind
cast when computing juniorState so you only read .uniform and .behaviour when
safe, falling back to { uniform: '', behaviour: '' } otherwise; update the code
that references juniorState to rely on this guarded assignment.
services/db.ts (2)

13-18: Duplicate type definition.

WeeklyMarksSnapshotEntry is also defined in components/weeklyMarksSavePlan.ts (line 7). Consider importing from a single source to avoid drift.

♻️ Suggested consolidation

Either:

  1. Export from weeklyMarksSavePlan.ts and import here, or
  2. Move shared types to a dedicated types file (e.g., types.ts or a new types/marks.ts)
-export type WeeklyMarksSnapshotEntry = {
-  memberId: string;
-  mark: Mark | null;
-};
+import { WeeklyMarksSnapshotEntry } from '../components/weeklyMarksSavePlan';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/db.ts` around lines 13 - 18, Duplicate type
WeeklyMarksSnapshotEntry/WeeklyMarksSaveEntry exists; remove the local
definition and import the canonical WeeklyMarksSnapshotEntry from the existing
declaration (the one in components/weeklyMarksSavePlan.ts) or move both to a
shared types module (e.g., types/marks.ts) and import from there, then re-export
WeeklyMarksSaveEntry as an alias of the imported WeeklyMarksSnapshotEntry to
preserve existing exports; update any imports that referenced the removed local
type to use the single exported symbol.

121-129: Consider simplifying the mark transformation.

The IIFE pattern for extracting and removing date is functional but adds cognitive overhead. A helper function or destructuring at the call site could improve readability.

♻️ Suggested simplification
     p_snapshot: snapshot.map(({ memberId, mark }) => ({
       memberId,
-      mark: mark
-        ? (() => {
-            const { date: _date, ...storedMark } = toStoredMark(mark, section);
-            return storedMark;
-          })()
-        : null,
+      mark: mark ? toStoredMarkWithoutDate(mark, section) : null,
     })),

Where toStoredMarkWithoutDate is a simple helper:

const toStoredMarkWithoutDate = (mark: Mark, section: Section) => {
  const { date: _date, ...rest } = toStoredMark(mark, section);
  return rest;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/db.ts` around lines 121 - 129, The p_snapshot mapping uses an IIFE
to call toStoredMark(mark, section) and strip its date field which is hard to
read; create a small helper (e.g., toStoredMarkWithoutDate(mark, section)) that
calls toStoredMark, destructures out date and returns the rest, then replace the
IIFE in the p_snapshot map with a call to that helper for clarity and reuse;
reference p_snapshot, toStoredMark, mark, and section when making the change.
components/weeklyMarksSavePlan.test.ts (2)

124-139: Consider expanding test coverage for normalizeEditableMarksForSave.

The current tests cover company marks and absent junior marks, but the implementation has additional branches (junior section with valid uniform/behaviour scores, empty string → 0 conversion). Additional test cases would strengthen confidence in the normalization logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/weeklyMarksSavePlan.test.ts` around lines 124 - 139, Add tests
inside the existing describe('normalizeEditableMarksForSave') suite to cover the
junior-mark branches: one test where a junior mark includes numeric uniformScore
and behaviourScore (e.g., uniformScore: '2', behaviourScore: '3') and assert
those are preserved and converted to numbers, and another test where
uniformScore/behaviourScore are empty strings and should be normalized to 0
(instead of ''). Use the function normalizeEditableMarksForSave and mirror the
existing test style to assert expected normalized output for both cases.

141-156: Consider adding edge case tests for areMarkListsEqual.

Currently only tests order-insensitivity. Consider adding tests for: differing list lengths (should return false), marks with same date but different scores, and junior marks with uniformScore/behaviourScore fields.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/weeklyMarksSavePlan.test.ts` around lines 141 - 156, Add edge-case
tests for areMarkListsEqual: add a test asserting false for differing list
lengths, a test asserting false for two marks with the same date but different
score values (e.g., score 5 vs 6), and a test covering junior-mark objects that
use uniformScore/behaviourScore fields (both equal should return true, differing
should return false). Place these new it() blocks alongside the existing
order-insensitivity test and reference the areMarkListsEqual function in each
assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/03-getting-started.md`:
- Around line 59-71: The docs currently state that only `npm run test:e2e`
requires the E2E credentials, but `npm run check:db-contract` also reads
`.env`/`.env.local` and requires the E2E_TEST_* variables for the auth check;
update the paragraph describing `npm run check:db-contract` to explicitly state
it requires E2E_TEST_* (the same test user/role/seeded data as `npm run
test:e2e`), and clarify that both `npm run check:db-contract` and `npm run
test:e2e` consume E2E_TEST_* env vars so developers must set them before running
the pre-ship step.

In `@package.json`:
- Line 10: The package.json scripts "check:db-contract" (and similarly
"test:e2e") use the Node CLI flag --env-file-if-exists which requires Node
v22.9.0+, causing CI (Node 20) failures; fix by either removing the unknown flag
and switching to --env-file (and ensure required env files are present),
updating the CI Node runtime to >=22.9.0, or wrap env-file loading in a small
bootstrap script that conditionally loads .env/.env.local before invoking
scripts so the CLI doesn’t rely on --env-file-if-exists.

In `@tests/e2e/smoke.e2e.ts`:
- Around line 111-121: In the finally block make the restore authoritative: use
openSectionSettings to navigate, await restoreSelect.inputValue and if different
await restoreSelect.selectOption and then await the Save button click
(page.getByRole('button', { name: 'Save' }).click), and explicitly wait for the
save to complete (e.g., await a network response or a visible confirmation)
before exiting; do not swallow errors—replace console.error with rethrowing the
restoreError so the test fails fast if cleanup fails.

---

Nitpick comments:
In @.github/workflows/ci-infrastructure.yml:
- Around line 33-60: Make the live-Supabase steps conditional so forks and
secretless CI still run static checks: add an if expression that requires all
four secrets to be present to each of the live steps ("Check required Supabase
client and smoke-test env vars", "Smoke-test Supabase client startup and runtime
config", "Run client-visible database contract smoke check", "Install Playwright
browser dependencies", and "Run browser smoke tests against live Supabase
data"); e.g. use if: ${{ secrets.VITE_SUPABASE_URL != '' &&
secrets.VITE_SUPABASE_ANON_KEY != '' && secrets.E2E_TEST_EMAIL != '' &&
secrets.E2E_TEST_PASSWORD != '' }} so the remaining steps (typecheck, test:run,
build) run unconditionally.

In `@components/DatePicker.tsx`:
- Around line 20-26: Remove the stale commented-out code related to the removed
showPicker() API: delete the commented declarations and function for inputRef,
handleIconClick and the showPicker() call in DatePicker.tsx, and replace them
with a single concise inline comment noting that programmatic opening via
showPicker was removed; reference the identifiers inputRef, handleIconClick, and
showPicker in the comment so future readers know what was intentionally removed.

In `@components/weeklyMarksSavePlan.test.ts`:
- Around line 124-139: Add tests inside the existing
describe('normalizeEditableMarksForSave') suite to cover the junior-mark
branches: one test where a junior mark includes numeric uniformScore and
behaviourScore (e.g., uniformScore: '2', behaviourScore: '3') and assert those
are preserved and converted to numbers, and another test where
uniformScore/behaviourScore are empty strings and should be normalized to 0
(instead of ''). Use the function normalizeEditableMarksForSave and mirror the
existing test style to assert expected normalized output for both cases.
- Around line 141-156: Add edge-case tests for areMarkListsEqual: add a test
asserting false for differing list lengths, a test asserting false for two marks
with the same date but different score values (e.g., score 5 vs 6), and a test
covering junior-mark objects that use uniformScore/behaviourScore fields (both
equal should return true, differing should return false). Place these new it()
blocks alongside the existing order-insensitivity test and reference the
areMarkListsEqual function in each assertion.

In `@components/weeklyMarksSavePlan.ts`:
- Line 67: The current cast of markState to JuniorMarkState when creating
juniorState is unsafe; add a runtime type guard (e.g., isJuniorMarkState) that
checks markState is an object and has string properties "uniform" and
"behaviour", then use that guard instead of a blind cast when computing
juniorState so you only read .uniform and .behaviour when safe, falling back to
{ uniform: '', behaviour: '' } otherwise; update the code that references
juniorState to rely on this guarded assignment.

In `@docs/04-deployment.md`:
- Around line 34-37: The checklist repeats the verb "Verify" for consecutive
items (the lines starting "Verify sign-in works for manually provisioned
users.", "Verify the production users have the expected roles in `profiles`.",
"Verify `settings` has seeded rows for both `company` and `junior`.", and
"Smoke-test auth, section settings persistence, member CRUD, marks entry, and
dashboard behavior against the live backend."); update these items to use varied
leading verbs (e.g., "Confirm sign-in works...", "Ensure production users have
the expected roles in `profiles`.", "Check `settings` contains seeded rows for
both `company` and `junior`.", and keep or slightly reword the final
"Smoke-test..." line) so the checklist reads more smoothly while preserving the
same checks and scope.

In `@scripts/check-db-contract.mjs`:
- Around line 81-83: The finally block currently awaits supabase.auth.signOut()
without guarding against failures, which could mask an earlier test error; wrap
the await supabase.auth.signOut() call in a try-catch inside the existing
finally, await it inside the try, and in the catch log the sign-out error (e.g.,
console.error or existing logger) but do not re-throw so the original test error
is preserved; specifically modify the finally that calls supabase.auth.signOut()
to a try { await supabase.auth.signOut(); } catch (err) { /* log sign-out
failure with context */ }.

In `@services/db.test.ts`:
- Around line 209-255: The tests currently only assert routing to 'members' and
absence of RPC but not the exact write arguments; update the two tests to assert
the concrete write parameters: for the updateBoy test, verify that
supabaseMock.from('members').update was called once with the expected partial
payload (only allowed member fields — e.g., name/squad/year/is_squad_leader
mapping from the input) and that the returned query used eq('id', 'member-1')
and eq('section', 'company'); for the deleteBoyById test, verify that
supabaseMock.from('members').delete was called with no payload (or called once)
and that its query used eq('id', 'member-1') and eq('section', 'junior'); keep
the existing checks that rpc was not called. Ensure you reference the chain
starting from supabaseMock.from so you assert .update/.delete call arguments and
subsequent .eq filters used by updateBoy and deleteBoyById.

In `@services/db.ts`:
- Around line 13-18: Duplicate type
WeeklyMarksSnapshotEntry/WeeklyMarksSaveEntry exists; remove the local
definition and import the canonical WeeklyMarksSnapshotEntry from the existing
declaration (the one in components/weeklyMarksSavePlan.ts) or move both to a
shared types module (e.g., types/marks.ts) and import from there, then re-export
WeeklyMarksSaveEntry as an alias of the imported WeeklyMarksSnapshotEntry to
preserve existing exports; update any imports that referenced the removed local
type to use the single exported symbol.
- Around line 121-129: The p_snapshot mapping uses an IIFE to call
toStoredMark(mark, section) and strip its date field which is hard to read;
create a small helper (e.g., toStoredMarkWithoutDate(mark, section)) that calls
toStoredMark, destructures out date and returns the rest, then replace the IIFE
in the p_snapshot map with a call to that helper for clarity and reuse;
reference p_snapshot, toStoredMark, mark, and section when making the change.

In `@tests/e2e/02-section-settings-workflow.md`:
- Around line 17-23: Update the "Restore the original meeting day value before
leaving the page." step in tests/e2e/02-section-settings-workflow.md to
explicitly persist and verify the cleanup: after resetting the meeting day back
to the original value perform the save action (e.g., trigger the test's
save/submit flow and wait for any success indicator), then reload the page (or
navigate away and back) and assert the meeting day equals the original value;
ensure the new assertions replace the current implied cleanup so the test fails
if the restore does not actually persist.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7f59dd80-992e-41bc-84d7-d6e05ddadb94

📥 Commits

Reviewing files that changed from the base of the PR and between 9f0cf6d and f2db80f.

📒 Files selected for processing (34)
  • .github/workflows/ci-infrastructure.yml
  • ARCHITECTURE.md
  • App.tsx
  • README.md
  • components/AccountSettingsPage.tsx
  • components/BoyMarksPage.tsx
  • components/DatePicker.tsx
  • components/Header.tsx
  • components/HomePage.tsx
  • components/SettingsPage.tsx
  • components/WeeklyMarksPage.tsx
  • components/weeklyMarksDateChange.test.ts
  • components/weeklyMarksDateChange.ts
  • components/weeklyMarksSavePlan.test.ts
  • components/weeklyMarksSavePlan.ts
  • docs/03-getting-started.md
  • docs/04-deployment.md
  • docs/06-data-and-services.md
  • docs/09-database-and-migrations.md
  • docs/10-database-security-model.md
  • hooks/useAppData.ts
  • hooks/useAuthAndRole.ts
  • hooks/useToastNotifications.ts
  • package.json
  • scripts/check-db-contract.mjs
  • services/db.test.ts
  • services/db.ts
  • services/dbModel.ts
  • services/dbSyncPlan.test.ts
  • services/dbSyncPlan.ts
  • services/settings.test.ts
  • services/settings.ts
  • tests/e2e/02-section-settings-workflow.md
  • tests/e2e/smoke.e2e.ts
💤 Files with no reviewable changes (6)
  • components/SettingsPage.tsx
  • components/HomePage.tsx
  • components/AccountSettingsPage.tsx
  • hooks/useAuthAndRole.ts
  • services/dbSyncPlan.ts
  • services/dbSyncPlan.test.ts

"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"check:db-contract": "node --env-file-if-exists=.env --env-file-if-exists=.env.local scripts/check-db-contract.mjs",
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Node version:"
node -v

echo
echo "Checking Node help for env-file flags:"
node --help | rg -n -- '--env-file|env-file-if-exists' || true

echo
echo "Inspecting affected npm scripts:"
jq -r '.scripts["check:db-contract"], .scripts["test:e2e"]' package.json

Repository: harrisonk0/BB-Manager

Length of output: 506


🏁 Script executed:

cat .github/workflows/ci-infrastructure.yml | head -50

Repository: harrisonk0/BB-Manager

Length of output: 1634


🌐 Web query:

Node.js --env-file-if-exists flag introduction version release notes

💡 Result:

--env-file-if-exists was introduced in Node.js v22.9.0 (added as a new CLI flag; same behavior as --env-file but it doesn’t error if the file is missing) [1][2].

Sources: Node.js CLI docs (shows “Added in: v22.9.0”) [1] and the Node.js v22.9.0 release notes (lists “src: add --env-file-if-exists flag”) [2].


Remove --env-file-if-exists flag or upgrade CI Node version to 22.9.0+.

The --env-file-if-exists flag was introduced in Node.js v22.9.0, but CI uses Node 20. Both check:db-contract and test:e2e scripts will fail when run in CI. Either downgrade to --env-file (which errors on missing files and requires manual env setup), upgrade CI to Node 22.9.0 or later, or restructure scripts to handle missing env files gracefully.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 10, The package.json scripts "check:db-contract" (and
similarly "test:e2e") use the Node CLI flag --env-file-if-exists which requires
Node v22.9.0+, causing CI (Node 20) failures; fix by either removing the unknown
flag and switching to --env-file (and ensure required env files are present),
updating the CI Node runtime to >=22.9.0, or wrap env-file loading in a small
bootstrap script that conditionally loads .env/.env.local before invoking
scripts so the CLI doesn’t rely on --env-file-if-exists.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not taking this change. The repo CI already passed with these scripts on the current runner, so this is not an actual compatibility failure in this PR. If the runner is later pinned to a Node version that does not support --env-file-if-exists, I would switch to a bootstrap wrapper instead of changing behavior speculatively now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@harrisonk0, understood — if CI is already passing on the current runner, the flag is supported and the concern doesn't apply here. Your plan to switch to a bootstrap wrapper if the runner is ever pinned to an older version is a sensible fallback. No further action needed.

Repository owner deleted a comment from coderabbitai bot Mar 22, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors marks persistence to RPC-driven snapshot saves, adds a Node smoke-check (check:db-contract) that signs in and validates current_app_role() plus seeded settings rows, moves auth props into Header from the hook, removes several "use client" directives, and documents seeded per-section settings updated in-place.

Changes

Cohort / File(s) Summary
CI & Scripts
/.github/workflows/ci-infrastructure.yml, package.json, scripts/check-db-contract.mjs
Added check:db-contract script and CI step to validate env, sign in, call current_app_role(), and assert seeded settings rows; adjusted test:e2e to load .env/.env.local.
Documentation & Runbooks
ARCHITECTURE.md, README.md, docs/03-getting-started.md, docs/04-deployment.md, docs/06-data-and-services.md, docs/09-database-and-migrations.md, docs/10-database-security-model.md, tests/e2e/02-section-settings-workflow.md
Clarified settings are pre-seeded (one company, one junior) and updated in-place; added check:db-contract to pre-ship checks; expanded smoke-test scope and RLS guidance; updated migrations/timestamps and E2E runbook.
Marks persistence & DB services
services/db.ts, services/dbModel.ts, services/dbSyncPlan.ts (removed), services/db.test.ts, services/dbModel.test.ts
Replaced per-member sync with RPC-based saves: added saveBoyMarks and saveWeeklyMarksSnapshot and alias; refactored validation to validateMarksForSection with duplicate-date detection; removed buildMemberMarkSyncPlan; added tests for RPC interactions and duplicate-date validation.
Weekly snapshot helpers & tests
components/weeklyMarksSavePlan.ts, components/weeklyMarksSavePlan.test.ts, types.ts
Introduced snapshot helpers (buildWeeklyMarksSnapshot, normalizeEditableMarksForSave, areMarkListsEqual) and WeeklyMarksSnapshotEntry type; updated tests to assert snapshot semantics and helper behavior.
UI components & pages
components/BoyMarksPage.tsx, components/WeeklyMarksPage.tsx, components/DatePicker.tsx, components/AccountSettingsPage.tsx, components/HomePage.tsx, components/SettingsPage.tsx
Boy marks editing now uses normalization helpers and snapshot saves; WeeklyMarksPage builds snapshots, derives dirty state from pending snapshot, confirms date changes via helper/modal, and short-circuits empty saves; removed several "use client" directives and dead code.
Auth & header handling
App.tsx, components/Header.tsx, hooks/useAuthAndRole.ts
App.tsx passes currentUser and userRole into Header; Header accepts these props and no longer calls useAuthAndRole(); hook removed a global window event listener and no longer returns user alias.
Settings service & tests
services/settings.ts, services/settings.test.ts, tests/e2e/smoke.e2e.ts
saveSettings now performs targeted update(...).eq('section', ...) against seeded rows (no upsert); tests and E2E smoke helpers updated to assert PATCH/POST and RPC interactions and to include settings save/restore flow.
Hooks & minor cleanup
hooks/useAppData.ts, hooks/useToastNotifications.ts
Removed "use client" directives and added trailing newlines; formatting/cleanup only.
New unit tests & helpers
components/weeklyMarksDateChange.ts, components/weeklyMarksDateChange.test.ts, services/db.test.ts, components/weeklyMarksSavePlan.test.ts
Added date-change guard helper and tests; new DB write-model tests covering RPC usage and snapshot behavior; expanded weekly-marks save-plan test coverage.
E2E smoke tests
tests/e2e/smoke.e2e.ts
Added helpers for section settings/weekly marks network-aware saves, test rework to ensure restoration in finally, and new section-settings save/restore E2E test.

Sequence Diagram(s)

sequenceDiagram
    actor User as User (Browser)
    participant UI as WeeklyMarksPage
    participant Logic as weeklyMarksSavePlan
    participant DB as Supabase RPC
    participant Auth as Supabase Auth

    User->>UI: Edit marks or click Save
    UI->>Logic: buildWeeklyMarksSnapshot({boys, marks, selectedDate, attendance})
    Logic->>Logic: normalizeEditableMarksForSave / areMarkListsEqual
    Logic-->>UI: snapshot (changes-only) or empty

    alt Snapshot Empty
        UI-->>User: Show "No changes to save"
    else Snapshot Has Changes
        UI->>DB: RPC save_weekly_marks_snapshot(snapshot)
        DB->>Auth: resolve current_app_role()
        Auth-->>DB: role
        DB-->>UI: RPC success
        UI->>UI: fetchBoyById() to reload marks
        UI-->>User: Show success toast
    end

    alt Change week with unsaved changes
        User->>UI: Click week nav
        UI->>UI: requestDateChange(nextDate)
        UI->>Logic: shouldConfirmWeeklyMarksDateChange({currentDate,nextDate,isDirty})
        Logic-->>UI: true
        UI-->>User: Show confirmation modal
        User->>UI: Confirm change
        UI->>UI: apply pendingDate, reset editedMarks
    end
Loading
sequenceDiagram
    participant CI as CI Pipeline
    participant Script as check-db-contract.mjs
    participant Supabase as Supabase Project
    participant Auth as Auth Service
    participant DB as Database Tables

    CI->>Script: npm run check:db-contract (loads .env/.env.local)
    Script->>Supabase: create anon client (VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY)
    Script->>Auth: signInWithPassword(E2E_TEST_EMAIL, E2E_TEST_PASSWORD)
    Auth-->>Script: session + user
    Script->>Supabase: rpc('current_app_role')
    Supabase-->>Script: role
    Script->>DB: select section, meeting_day from settings where section IN ('company','junior')
    DB-->>Script: rows (expect exactly 2)
    Script->>Script: validate meeting_day ∈ [0..6]
    Script->>Auth: signOut (finally)
    Script-->>CI: exit success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I nibbled code and stitched a patch,

Snapshots now save with a gentle scratch.
Settings stay sown, not born anew,
Dates ask twice before they bid adieu,
A rabbit's hop — tests green and true.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Production-readiness audit remediation' accurately reflects the main objective of the PR, which addresses a broad set of production-readiness fixes including security, documentation, CI/testing improvements, and functionality refinements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/production-readiness-audit

Comment @coderabbitai help to get the list of available commands and usage tips.

@harrisonk0
Copy link
Copy Markdown
Owner Author

Addressed in 6d7a9c7:

  • removed the stale commented showPicker path from DatePicker
  • guarded signOut() in check-db-contract so cleanup cannot mask the original failure
  • expanded the section-settings runbook cleanup expectation
  • tightened services/db.test.ts assertions for member update/delete writes
  • expanded weeklyMarksSavePlan coverage and added a runtime guard for junior mark state
  • moved WeeklyMarksSnapshotEntry into types.ts so the component and service share one definition

Not taking a few review suggestions:

  • live-Supabase CI steps remain required on this repo by design; making them conditional would change CI policy, not fix a defect
  • the repeated "Verify" wording in deployment docs is style-only and not worth churning in this PR
  • I kept the inline toStoredMark(...)/strip date transformation in services/db.ts unchanged because it is readable enough and has no behavioral risk

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
components/WeeklyMarksPage.tsx (1)

203-219: ⚠️ Potential issue | 🟡 Minor

isDirty is still sticky after reverting to the original snapshot.

The no-op save path clears the flag only after the user clicks Save. If they edit back to the original values and then change weeks, requestDateChange() still sees isDirty === true and opens the discard modal even though buildWeeklyMarksSnapshot() is empty. Use the snapshot itself to drive both the modal gate and the Save FAB.

Also applies to: 257-268

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/WeeklyMarksPage.tsx` around lines 203 - 219, requestDateChange
currently relies on the boolean isDirty which stays true even if the user
reverts edits; change the gating logic to compute the snapshot via
buildWeeklyMarksSnapshot() and use its emptiness instead of isDirty when calling
shouldConfirmWeeklyMarksDateChange and when rendering/enabling the Save FAB.
Specifically, update requestDateChange to pass the computed snapshot (or a
boolean derived from it) into shouldConfirmWeeklyMarksDateChange and only call
setPendingDate/setIsDateChangeConfirmOpen when the snapshot indicates real
changes; likewise, drive the Save FAB enabled/visible state from
buildWeeklyMarksSnapshot() emptiness rather than the isDirty flag so reverting
edits clears the modal gate and save affordance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/check-db-contract.mjs`:
- Line 80: The log line prints a user's email (signInData.user.email) which
leaks PII; update the console.log that uses signInData.user.email and role to
either remove the email or redact it (e.g., only include role or a constant like
"[redacted]") so the message becomes something like "Database contract smoke
check passed for [role]" or "Database contract smoke check passed for [redacted]
(role)"; modify the console.log call in scripts/check-db-contract.mjs
accordingly to stop emitting the email.

In `@services/db.ts`:
- Around line 25-59: buildMemberMarksPatch assumes mark.date is unique but
validateMarksForSection currently doesn’t enforce this, causing duplicate
upserts; add a uniqueness guard that detects duplicate mark.date values and
rejects (throw an error) before patch construction. Specifically, in the call
path that uses buildMemberMarksPatch (e.g., saveBoyMarks) and inside
services/dbModel.ts.validateMarksForSection, validate the incoming marks array
for duplicate dates (by building a Set of dates or a Map) and throw a clear
validation error if any date appears more than once so buildMemberMarksPatch
only ever receives unique-dated marks.

In `@tests/e2e/smoke.e2e.ts`:
- Around line 43-48: The network waiter in saveSettingsAndWait is too permissive
(accepts both POST and PATCH) and should only detect the seeded-row update path;
update the predicate passed to page.waitForResponse in saveSettingsAndWait to
only match PATCH requests whose URL includes '/rest/v1/settings' (remove POST
from the allowed methods) so the test fails on insert/upsert regressions; keep
the rest of the wait logic and the saveResponsePromise usage intact.
- Around line 115-137: The finally block for restoring the 'Weekly Meeting Day'
(working with meetingDaySelect, restoreSelect, saveSettingsAndWait,
openSectionSettings, originalValue, nextValue) must not throw directly; instead
capture any error from the try body and any error from the cleanup, then after
the finally rethrow appropriately so the original test failure isn't lost.
Concretely, introduce two local error holders (e.g., originalError and
restoreError), assign originalError if the main try throws, assign restoreError
if the cleanup fails (but do not throw inside finally), and after the finally
rethrow either the originalError (if present) or the restoreError, or combine
them into a single error message so both failures are preserved.

---

Duplicate comments:
In `@components/WeeklyMarksPage.tsx`:
- Around line 203-219: requestDateChange currently relies on the boolean isDirty
which stays true even if the user reverts edits; change the gating logic to
compute the snapshot via buildWeeklyMarksSnapshot() and use its emptiness
instead of isDirty when calling shouldConfirmWeeklyMarksDateChange and when
rendering/enabling the Save FAB. Specifically, update requestDateChange to pass
the computed snapshot (or a boolean derived from it) into
shouldConfirmWeeklyMarksDateChange and only call
setPendingDate/setIsDateChangeConfirmOpen when the snapshot indicates real
changes; likewise, drive the Save FAB enabled/visible state from
buildWeeklyMarksSnapshot() emptiness rather than the isDirty flag so reverting
edits clears the modal gate and save affordance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e4bb035-19a6-4f5f-a077-359d7e50a99b

📥 Commits

Reviewing files that changed from the base of the PR and between f2db80f and 6d7a9c7.

📒 Files selected for processing (11)
  • components/DatePicker.tsx
  • components/WeeklyMarksPage.tsx
  • components/weeklyMarksSavePlan.test.ts
  • components/weeklyMarksSavePlan.ts
  • docs/03-getting-started.md
  • scripts/check-db-contract.mjs
  • services/db.test.ts
  • services/db.ts
  • tests/e2e/02-section-settings-workflow.md
  • tests/e2e/smoke.e2e.ts
  • types.ts
✅ Files skipped from review due to trivial changes (4)
  • types.ts
  • tests/e2e/02-section-settings-workflow.md
  • services/db.test.ts
  • components/weeklyMarksSavePlan.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/03-getting-started.md
  • components/weeklyMarksSavePlan.test.ts

@harrisonk0
Copy link
Copy Markdown
Owner Author

Addressed in 9b94f69:

  • weekly pending-change state is now derived from the actual snapshot, so reverted edits no longer trigger discard prompts or leave the save affordance stuck on
  • check:db-contract no longer logs the authenticated email
  • duplicate mark dates are rejected at validation time and covered by tests
  • the section-settings smoke waiter now pins the seeded-row PATCH path
  • the smoke-test cleanup flow now preserves both the primary failure and any restore failure instead of throwing from finally

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/WeeklyMarksPage.tsx (1)

350-350: ⚠️ Potential issue | 🟡 Minor

Remove double semicolon.

continue;; has an extraneous semicolon.

🔧 Fix
-        stats[squad] = { present: 0, total: 0, percentage: 0 };
-        continue;;
+        stats[squad] = { present: 0, total: 0, percentage: 0 };
+        continue;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/WeeklyMarksPage.tsx` at line 350, There is an extraneous semicolon
after a continue statement ("continue;;") in WeeklyMarksPage.tsx; replace
"continue;;" with a single "continue;" (or simply "continue" if in a context
where semicolons are omitted) to remove the double semicolon and restore correct
syntax.
🧹 Nitpick comments (2)
tests/e2e/smoke.e2e.ts (1)

128-142: Consider guarding openSectionSettings against already-open state.

If the main test body completes successfully, Section Settings will already be open when the finally block runs. Calling openSectionSettings(page) again at line 130 clicks the button, which could toggle the modal closed if the UI treats it as a toggle, potentially causing the subsequent assertions to fail spuriously.

If the UI button always opens (never toggles), this is fine. Otherwise, consider checking whether the heading is already visible before clicking:

🛡️ Optional guard
     } finally {
       try {
+        const settingsHeading = page.getByRole('heading', { name: 'Section Settings' });
+        if (!(await settingsHeading.isVisible())) {
+          await openSectionSettings(page);
+        }
-        await openSectionSettings(page);
         const restoreSelect = page.getByLabel('Weekly Meeting Day');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/smoke.e2e.ts` around lines 128 - 142, The finally block can call
openSectionSettings(page) even when the Section Settings modal is already open,
which may toggle it closed; update the cleanup to first check whether the
settings modal is already visible (e.g., query for the Section Settings heading
or dialog role) and only call openSectionSettings(page) when it is not visible,
then proceed to read/restore Weekly Meeting Day via restoreSelect and
saveSettingsAndWait; reference openSectionSettings(page), the restoreSelect
(page.getByLabel('Weekly Meeting Day')), saveSettingsAndWait, and restoreError
to keep existing error capture.
components/WeeklyMarksPage.tsx (1)

45-45: Remove dead isDirty state.

The isDirty state value is never read—hasPendingChanges derived from the snapshot is used everywhere instead. The setIsDirty calls (lines 104, 163, 195, 214, 274, 285) cause unnecessary re-renders without affecting behavior. Consider removing this state entirely along with all setIsDirty calls.

♻️ Remove dead state and its setter calls
-  const [, setIsDirty] = useState(false); // Retained for local input change tracking; navigation gating is derived from the snapshot.

Then remove all setIsDirty(...) calls throughout the file (lines 104, 163, 195, 214, 274, 285).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/WeeklyMarksPage.tsx` at line 45, Remove the dead isDirty state and
its setter to avoid unnecessary renders: delete the useState declaration "const
[, setIsDirty] = useState(false);" in the WeeklyMarksPage component and remove
every call to setIsDirty(...) inside that component (these calls occur in
handlers that already rely on hasPendingChanges derived from the snapshot).
Ensure no other code references isDirty or setIsDirty afterwards; if there are,
replace those references to use hasPendingChanges or the appropriate
snapshot-derived value instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@components/WeeklyMarksPage.tsx`:
- Line 350: There is an extraneous semicolon after a continue statement
("continue;;") in WeeklyMarksPage.tsx; replace "continue;;" with a single
"continue;" (or simply "continue" if in a context where semicolons are omitted)
to remove the double semicolon and restore correct syntax.

---

Nitpick comments:
In `@components/WeeklyMarksPage.tsx`:
- Line 45: Remove the dead isDirty state and its setter to avoid unnecessary
renders: delete the useState declaration "const [, setIsDirty] =
useState(false);" in the WeeklyMarksPage component and remove every call to
setIsDirty(...) inside that component (these calls occur in handlers that
already rely on hasPendingChanges derived from the snapshot). Ensure no other
code references isDirty or setIsDirty afterwards; if there are, replace those
references to use hasPendingChanges or the appropriate snapshot-derived value
instead.

In `@tests/e2e/smoke.e2e.ts`:
- Around line 128-142: The finally block can call openSectionSettings(page) even
when the Section Settings modal is already open, which may toggle it closed;
update the cleanup to first check whether the settings modal is already visible
(e.g., query for the Section Settings heading or dialog role) and only call
openSectionSettings(page) when it is not visible, then proceed to read/restore
Weekly Meeting Day via restoreSelect and saveSettingsAndWait; reference
openSectionSettings(page), the restoreSelect (page.getByLabel('Weekly Meeting
Day')), saveSettingsAndWait, and restoreError to keep existing error capture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5d73d910-f704-44f5-8104-462dd2741c30

📥 Commits

Reviewing files that changed from the base of the PR and between 6d7a9c7 and 9b94f69.

📒 Files selected for processing (6)
  • components/WeeklyMarksPage.tsx
  • scripts/check-db-contract.mjs
  • services/db.test.ts
  • services/dbModel.test.ts
  • services/dbModel.ts
  • tests/e2e/smoke.e2e.ts
✅ Files skipped from review due to trivial changes (2)
  • scripts/check-db-contract.mjs
  • services/db.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • services/dbModel.ts

@harrisonk0
Copy link
Copy Markdown
Owner Author

Addressed the final three review items in commit 3cc1233:

  • removed the stray continue;; typo in WeeklyMarksPage
  • deleted the now-dead isDirty state/setter calls after moving pending-change detection to the snapshot
  • added ensureSectionSettingsOpen(page) in the Playwright cleanup path so the restore flow does not depend on blindly reopening settings

Validation after the change: npm run typecheck, targeted npm run test:run, and npm run test:e2e -- --list all passed locally.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/WeeklyMarksPage.tsx (1)

288-288: today can become stale if the page stays open past midnight.

The empty dependency array means today is computed once on mount. If a user keeps the page open across midnight, isPastDate (line 381) will use a stale value. The isLocked effect (lines 66-70) also won't re-run since it only depends on selectedDate.

This is a low-probability edge case, but for consistency you could derive today inline or refresh it on visibility change.

♻️ Optional: derive inline or refresh on focus
- const today = useMemo(() => new Date().toISOString().split('T')[0], []);
+ const today = new Date().toISOString().split('T')[0];

Or, if you want to avoid recomputing on every render, consider refreshing when the document regains focus.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/WeeklyMarksPage.tsx` at line 288, The computed `today` value is
created once with useMemo and can become stale across midnight; change its usage
so it is computed fresh when needed (e.g., derive `today` inline instead of via
`useMemo`) or update it on visibility/focus so dependent logic like `isPastDate`
and the `isLocked` effect that depends on `selectedDate` re-evaluates;
specifically, replace the `const today = useMemo(...)` pattern in
WeeklyMarksPage with either an inline `new Date().toISOString().split('T')[0]`
where `today` is used (so `isPastDate` always sees current date) or add a small
effect that updates `today` on `visibilitychange`/`focus` and use that state
variable so `isPastDate` and the `isLocked` effect re-run when the day changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/smoke.e2e.ts`:
- Around line 95-105: The test currently calls the save_weekly_marks_snapshot
RPC (detected via saveResponsePromise) and writes a new live snapshot but never
restores the original marks, leaving persistent dated fixtures; after the Save
Marks step (after saveResponse.ok() assertion) call the same RPC to write the
original/currentValue back (use page.request.post or the app UI flow) and await
a matching response like the existing saveResponsePromise pattern (POST to
'/rest/v1/rpc/save_weekly_marks_snapshot') to ensure the cleanup completed
before the test exits; locate the save logic around
saveResponsePromise/scoreInput and add a restore call that sends the original
value for marksDate/values and waits for the response.

---

Nitpick comments:
In `@components/WeeklyMarksPage.tsx`:
- Line 288: The computed `today` value is created once with useMemo and can
become stale across midnight; change its usage so it is computed fresh when
needed (e.g., derive `today` inline instead of via `useMemo`) or update it on
visibility/focus so dependent logic like `isPastDate` and the `isLocked` effect
that depends on `selectedDate` re-evaluates; specifically, replace the `const
today = useMemo(...)` pattern in WeeklyMarksPage with either an inline `new
Date().toISOString().split('T')[0]` where `today` is used (so `isPastDate`
always sees current date) or add a small effect that updates `today` on
`visibilitychange`/`focus` and use that state variable so `isPastDate` and the
`isLocked` effect re-run when the day changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31e772af-82b5-4dc1-ad48-4e5732911b02

📥 Commits

Reviewing files that changed from the base of the PR and between 9b94f69 and 3cc1233.

📒 Files selected for processing (2)
  • components/WeeklyMarksPage.tsx
  • tests/e2e/smoke.e2e.ts

@harrisonk0
Copy link
Copy Markdown
Owner Author

Addressed the latest two real review findings in commit bd2b703:

  • the weekly-marks smoke test now restores the original live score after verifying persistence, and it preserves both the primary failure and any cleanup failure
  • WeeklyMarksPage no longer relies on a mount-time today value; it refreshes the current date on focus/visibility and on a short interval so overnight tabs re-evaluate the lock state correctly

I did not act on the older repeated comments in that review because they were already resolved on HEAD before this push (continue;;, dead isDirty, and the section-settings open guard).

Validation after the change:

  • npm run typecheck
  • E2E_TEST_EMAIL=... E2E_TEST_PASSWORD=... npm run test:e2e

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/e2e/smoke.e2e.ts (1)

126-141: Consider adding an ensureWeeklyMarksPageOpen helper for consistency.

The section settings test uses ensureSectionSettingsOpen for idempotent navigation in the restore path, but the weekly marks restore unconditionally calls openWeeklyMarksPage. While this works (since clicking the nav button is likely idempotent), an ensure* variant would be more explicit and consistent with the settings pattern.

♻️ Optional consistency improvement
+const ensureWeeklyMarksPageOpen = async (page: Page) => {
+  const heading = page.getByRole('heading', { name: 'Weekly Marks' });
+
+  if (!(await heading.isVisible())) {
+    await openWeeklyMarksPage(page);
+  }
+};

Then in the restore block:

       try {
-        await openWeeklyMarksPage(page);
+        await ensureWeeklyMarksPageOpen(page);
         await dateInput.fill(marksDate);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/smoke.e2e.ts` around lines 126 - 141, Add an idempotent navigation
helper ensureWeeklyMarksPageOpen and use it in the restore/finally path instead
of calling openWeeklyMarksPage unconditionally; implement
ensureWeeklyMarksPageOpen to detect whether the weekly marks view is already
active (or verify by selector used in openWeeklyMarksPage) and only click the
nav button when needed, then update the finally block to call
ensureWeeklyMarksPageOpen(page) before interacting with dateInput,
restoreScoreInput and saveWeeklyMarksAndWait to mirror the
ensureSectionSettingsOpen pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/smoke.e2e.ts`:
- Around line 126-141: Add an idempotent navigation helper
ensureWeeklyMarksPageOpen and use it in the restore/finally path instead of
calling openWeeklyMarksPage unconditionally; implement ensureWeeklyMarksPageOpen
to detect whether the weekly marks view is already active (or verify by selector
used in openWeeklyMarksPage) and only click the nav button when needed, then
update the finally block to call ensureWeeklyMarksPageOpen(page) before
interacting with dateInput, restoreScoreInput and saveWeeklyMarksAndWait to
mirror the ensureSectionSettingsOpen pattern.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73ae1ec4-6297-41d1-b76d-29258c7c303c

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc1233 and bd2b703.

📒 Files selected for processing (2)
  • components/WeeklyMarksPage.tsx
  • tests/e2e/smoke.e2e.ts

@harrisonk0 harrisonk0 merged commit 551865b into main Mar 22, 2026
6 checks passed
@harrisonk0 harrisonk0 deleted the fix/production-readiness-audit branch March 22, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant